%%%-------------------------------------------------------------------
%%% @doc  
%%% @end
%%%-------------------------------------------------------------------
-module(resource).

-behaviour(gen_fsm).
%%--------------------------------------------------------------------
%% Include files
%%--------------------------------------------------------------------
-include("drproxy_client.hrl").

%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
-export([start_link/1, idle/2, check_loop/1, check/1, alloc/2,
		 process/2, process/3, local_name/1, restart/1, monitor/1]).

%%--------------------------------------------------------------------
%% gen_fsm callbacks
%%--------------------------------------------------------------------

-export([init/1, handle_event/3, handle_sync_event/4,
         handle_info/3, terminate/3, code_change/4]).

%%--------------------------------------------------------------------
%% record definitions
%%--------------------------------------------------------------------

-record(state, {n, receiver = none, attempts = 0, problems = 0,
				last_monitored = false, last_restarted = false,
				check_loop, verbose = true, monitor_attempts = 5,
				completed = 0}).

%%--------------------------------------------------------------------
%% macro definitions
%%--------------------------------------------------------------------
-define(SERVER, ?MODULE).

%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% @doc Starts the server.
%% @spec start_link() -> {ok, pid()} | {error, Reason}
%% @end
%%--------------------------------------------------------------------

start_link(N) -> 
	gen_fsm:start_link({local, local_name(N)}, ?MODULE, N, []). 

%%====================================================================
%% gen_server callbacks
%%====================================================================

init(N) ->
    process_flag(trap_exit, true),
	CheckLoop = spawn_link(?SERVER, check_loop, [self()]),
    {ok, idle, #state{n = N, check_loop = CheckLoop}, 1000}.

handle_event(monitor, _StateName, State) ->
    force_monitor(State);

handle_event(restart, _StateName, State) ->
    force_restart(State).

handle_sync_event(Event, From, StateName, State) ->
    {next_state, StateName, State}.

handle_info({'EXIT', Pid, Reason}, StateName, State) -> 
	case State#state.check_loop == Pid of
		true ->
			CheckLoop = spawn_link(?SERVER, check_loop, [self()]),
			{next_state, StateName, State#state{check_loop = CheckLoop}};
		false ->
			error_logger:error_msg("Unexpected process (~w) died: resource - ~p~n", [Pid, Reason]),
			{next_state, StateName, State}
	end;
handle_info(Info, StateName, State) ->
	error_logger:error_msg("Invalid info (resource): ~w~n", [Info]),
	{next_state, StateName, State}.

terminate(_Reason, _StateName, _State) ->
    ok.

code_change(_OldVsn, StateName, State, _Extra) ->
    {ok, StateName, State}.

%%====================================================================
%%% API
%%====================================================================

alloc(Pid, Receiver) ->
	gen_fsm:send_event(Pid, {alloc, Receiver}).

process(Pid, Server, Bin) ->
	gen_fsm:send_event(Pid, {get, Server, Bin}).
	
check(Pid) ->
	gen_fsm:send_event(Pid, check).
	
idle({alloc, Receiver}, State) ->
    {next_state, process, State#state{receiver = Receiver}};
idle(check, State) ->
	check(idle, State);
idle(timeout, State) ->
	case State#state.last_monitored of
		false ->
			force_monitor(State);
		_ ->
			{next_state, idle, State}
	end;
idle(Message, State) ->
	error_logger:error_msg("Unexpected idle message: resource - ~p~n", [Message]),
	{next_state, idle, State}.

process(check, State) ->
	{next_state, process, State};
process({get, Server, Bin}, State) ->
	HttpRequest = binary_to_list(Bin),
	receive after drproxy_client:config(delay) ->
		Response = get_binary_from_server(HttpRequest, State#state.n, now(), State#state.attempts),
		State#state.receiver ! Response,
		finish_process(State, drproxy_client:config(restart_after_completed))
	end;
process(Message, State) ->
	error_logger:error_msg("Unexpected process message: resource - ~p~n", [Message]),
	{next_state, process, State}.

finish_process(State, infinity) ->
	resource_server:release(self()),
	{next_state, idle, State#state{receiver = none, attempts = 0, completed = State#state.completed + 1}};
finish_process(State, RestartAfterCompleted) ->
	case State#state.completed > RestartAfterCompleted of
		true -> restart(self()), {next_state, idle, State#state{receiver = none, attempts = 0}};
		false -> finish_process(State, infinity)
	end.
	
monitor(Pid) ->
	gen_fsm:send_all_state_event(Pid, monitor).

restart(State, Cpu, Ram) ->
	error_logger:info_msg("Restarting ~w because Cpu = ~s and Ram = ~s~n", [State#state.n, Cpu, Ram]),
	restart_now(State).

restart(Pid) ->
	gen_fsm:send_all_state_event(Pid, restart).
	
force_restart(State) ->
	error_logger:info_msg("Forcing a restart of ~w~n", [State#state.n]),
	restart_now(State).

restart_now(State) ->
	resource_server:monitor(self(), State#state.n),
	flush_messages(),
	KillCmd = lists:flatten(io_lib:format("kill -9 `cat ~s`", [drproxy_client:pid_file(State#state.n)])),
	os:cmd(KillCmd),
	RmCmd = lists:flatten(io_lib:format("rm ~s", [drproxy_client:pid_file(State#state.n)])),
	os:cmd(RmCmd),
	StartCmd = lists:flatten(io_lib:format(drproxy_client:config(start_script), [drproxy_client:client_port(State#state.n), drproxy_client:client_port(State#state.n)])),
	error_logger:info_msg("Starting ~w: ~s~n", [State#state.n, StartCmd]),
	os:cmd(StartCmd),
	monitor1(State#state{last_restarted = now(), completed = 0, monitor_attempts = 0}).
	
check_loop(Pid) ->
	catch receive after drproxy_client:config(monitor_frequency) ->
		check(Pid)
	end,
	check_loop(Pid).

%%====================================================================
%%% Internal functions
%%====================================================================

force_monitor(State) ->
	case State#state.last_monitored of
	 	false ->
			force_monitor1(State);
		LastMonitored ->
			case timer:now_diff(now(), LastMonitored)/1000000 < drproxy_client:config(startup_delay) of
				true ->
          error_logger:info_msg("We already monitored ~w within the startup delay~n", [State#state.n]),
					case State#state.last_restarted of
						false ->
							force_restart(State);
						LastRestarted ->
							case timer:now_diff(now(), LastRestarted)/1000000 < drproxy_client:config(startup_delay) of
								true ->
                  error_logger:info_msg("We restarted ~w within the startup delay, ignoring the monitor attempt~n", [State#state.n]),
									{next_state, idle, State};
								false ->
                  error_logger:info_msg("We haven't restarted ~w within the startup delay, doing the monitor~n", [State#state.n]),
									force_monitor1(State)
							end
					end;
				false ->
					force_monitor1(State)
			end
	end.

force_monitor1(State) ->
	case State#state.monitor_attempts == 6 of
		true -> 
      error_logger:info_msg("Restarting ~w because of 6 unsuccessful monitors in a row~n", [State#state.n]),
			restart_now(State);
		false ->
			case State#state.verbose of
				true -> error_logger:info_msg("Monitoring ~w~n", [State#state.n]);
				false -> true
			end,
			resource_server:monitor(self(), State#state.n),
			flush_messages(),
			monitor1(State)
	end.

monitor1(State) ->
	case get_binary_from_server("GET / HTTP/1.1\r\n\r\n", State#state.n, now(), 0) of
		{error, _, _, _} ->
			receive after 10000 ->
				gen_fsm:send_all_state_event(self(), monitor),
				{next_state, idle, State#state{verbose = false, monitor_attempts = State#state.monitor_attempts + 1}}
			end;
		{ok, _, _, _} ->
			flush_checks(),
			resource_server:alive(self(), State#state.n),
			{next_state, idle, State#state{verbose = true, last_monitored = now(), monitor_attempts = 0}}
	end.

local_name(N) ->
	list_to_atom(atom_to_list(?MODULE) ++ integer_to_list(N)).

flush_checks() ->
	receive
		{'$gen_event', check} ->
			flush_checks()
	after 0 ->
		true
	end.

flush_messages() ->
	receive
		{'$gen_event', {get, Server, Bin}} ->
			process_server:process(Server, Bin),
			flush_messages();
		_ ->
			flush_messages()
	after 0 ->
		true
	end.

check(StateName, State) ->
	case State#state.last_restarted of
		false ->
			check1(StateName, State);
		LastRestarted ->
			case timer:now_diff(now(), LastRestarted)/1000000 < drproxy_client:config(startup_delay) of
				true ->
					{next_state, StateName, State};
				false ->
					check1(StateName, State)
			end
	end.

check1(StateName, State) ->
	flush_checks(),
	PsCmd = lists:flatten(io_lib:format("ps -o %cpu,%mem -p `cat ~s`", [drproxy_client:pid_file(State#state.n)])),
	Ps = os:cmd(PsCmd),
	case regexp:match(Ps, "([0-9\.]+  [0-9\.]+)") of
		{match, Start, End} ->
			{ok, [Cpu, Ram]} = regexp:split(string:substr(Ps, Start, End), "  "),
			CpuOk = list_to_float(Cpu) < drproxy_client:config(max_cpu),
			RamOk = list_to_float(Ram) < drproxy_client:config(max_ram),
			case CpuOk and RamOk of
				false ->
					case State#state.problems >= drproxy_client:config(check_times) of
						true ->
							restart(State, Cpu, Ram);
						false ->
							receive after drproxy_client:config(monitor_frequency) -> true end,
							{next_state, StateName, State#state{problems = State#state.problems + 1}}
					end;
				_ ->
					{next_state, StateName, State#state{problems = 0}}
			end;
		nomatch ->
			{next_state, StateName, State}
	end.

get_binary_from_server(Request, N, Timestamp, Attempts) ->
	case gen_tcp:connect("127.0.0.1", drproxy_client:client_port(N), [binary, {packet, 0}, {active, false}]) of
		{ok, Socket} ->
			tcp_server:send_data(Socket, Request),
			Reply = full_reply(Socket, N, Timestamp, Request, Attempts),
			gen_tcp:close(Socket),
			Reply;
		{error, E} ->
			{error, N, request, E}
	end.

full_reply(Socket, N, Timestamp, Request, Attempts) ->
	case wait_reply(Socket, "", Request, Attempts, drproxy_client:config(response_timeout)) of
		{error, K, R} ->
			{error, N, K, R};
		M ->
			{ok, N, M, timer:now_diff(now(), Timestamp)/1000000}
	end.

wait_reply(Socket, Reply, Request, Attempts, Timeout) ->
	case gen_tcp:recv(Socket, 0, Timeout) of
		{ok, Bin} ->
			wait_reply(Socket, Reply ++ binary_to_list(Bin), Request, Attempts, Timeout);
		{error, _} ->
			case length(Reply) == 0 of
				true ->
					case Attempts > 0 of
						true ->
							{error, request, bad_header};
						false ->
							{error, request, econnrefused}
					end;
				false ->
					list_to_binary(Reply)
			end
	end.
